/********************************************************
info: 实现字符串的切割功能 识别字符串中的子字符串进行切割  
	如一个字符串用"|"进行分割，识别所有的分割模块并获取信息
	实现字符串的拼接
data: 2022/02/10
author: hlp
********************************************************/

//用到了to_string 把int转为string 编译的时候用g++ 以及-std=c++11
//可以用itoa替代用到了to_string，这里我不关注这个细节了
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <iostream>	//这里使用了c++的to_string把int类型转为string 可以用c语言的itoa
#include <string>
using namespace std;

/********************************************************
网络传输中，对字符流的处理非常重要，建立在我们制定的特定的规范上
===》除了关注一个包的完整接收（粘包半包问题），还需要关注数据本身
===》1：类似tcp/ip协议栈那样，用特定的协议栈，特定的字节表示特定的含义进行解析（固定字节大小表示含义）
===》2：我们可以自定义规范，如用特定的字符/字符串进行识别，分割不同字符所表示的含义（如 msg_type|other_type|msg_len|msg_data）.
=======>这里不关注为了保证一个包的完整可靠，而增加的checksum以及头尾特定标识符，可以自行设计
*********************************************************/
//字符串拼接函数
void concatenate_string();
//场景一般是：按照特定的协议对获取到的网络数据进行解析 
//比如，类似tcp/ip协议栈，按照协议栈格式进行解析。
//比如，可以自己协商，用|或者特定字符进行分割，实现解析 
void parse_string_test();
int main()
{
	//字符串拼接的几种方案测试  memcpy,strcat,以及sprintf  C++中string的相关操作不讨论，+，append等。。。
	concatenate_string();
	//按照特定的协议对收到的字符串进行解析，或者分割
	parse_string_test();
	return 0;
}


//假设我们已经做了半包粘包处理，获得一个包中实际数据，并且进行了必要的checksum校验，获得一个包数据 "msg_type|other_type|msg_len|msg_data"
//按照特定的格式对该数据进行解析，获取到必要的信息

//1:使用memcpy实现字符串的拼接 splicing test of string1 and string2
int use_memcpy_splic_string();
//2:使用c库函数实现字符串的拼接 注意stri以'\0'识别终结 如果处理网络数据有'\0'的场景要注意
int use_clibrary_strcat_splic_string();
//3：使用sprintf进行拼接  对于各种类型的数据拼接成字符串很实用 ===》用的最多
int use_sprintf_splic_string();
//拼接字符串的几种方案测试
void concatenate_string()
{
	//C语言字符串的操作一般要先对char*指针申请特定大小的内存
	//这里我了解的字符串拼接有两种方案 
	//	1： 使用malloc申请固定大小，memcpy实现拷贝赋值
	//	2： 使用c库函数，strcpy, strcat
	//	3： 使用sprintf、sprintf_s

	//1:使用memcpy实现字符串的拼接 splicing test of string1 and string2
	use_memcpy_splic_string();
	//2：使用c库函数strcpy，strcat测试一下
	use_clibrary_strcat_splic_string();
	//3：使用sprintf进行拼接  对于各种类型的数据拼接成字符串很实用 ===》用的最多
	use_sprintf_splic_string();
}

//1:使用memcpy实现字符串的拼接 splicing test of string1 and string2
int use_memcpy_splic_string()
{
printf("use_memcpy_splic_string test:\n");
	const char* str1 = "splicing test of ";
	const char* str2 = "string";
	const char* and1 = " and ";
	int one = 1;
	int two = 2;
	//这里用到了C++中的to_string函数  c语言可以用itoa,这里主要是理解字符串拼接，，， 有int类型的字符串转换拼接常用sprintf
	int size = strlen(str1) + strlen(str2) * 2 + (strlen(to_string(one).c_str())) + strlen(and1) + (strlen(to_string(two).c_str())) + 1;
	printf("\tget the len is : %d %lu\n", size, strlen("splicing test of string1 and string2"));

	int pos = 0;
	char* result = (char*)malloc(size);
	if (result == NULL)
	{
		return -1;
	}
	memset(result, '\0', size);
	memcpy(result, str1, strlen(str1));
	pos += strlen(str1);
	memcpy(result + pos, str2, strlen(str2));
	pos += strlen(str2);
	memcpy(result + pos, to_string(one).c_str(), strlen(to_string(one).c_str()));
	pos += strlen(to_string(one).c_str());
	memcpy(result + pos, and1, strlen(and1));
	pos += strlen(and1);
	memcpy(result + pos, str2, strlen(str2));
	pos += strlen(str2);
	memcpy(result + pos, to_string(two).c_str(), strlen(to_string(two).c_str()));
	pos += strlen(to_string(two).c_str());
	printf("\tthe result is [%lu][%s]  \n", strlen(result), result);
	printf("\tpos is [%d] \n", pos);
	if (result != NULL)
	{
		free(result);
		result = NULL;
	}
	return 0;
}

//2:使用c库函数实现字符串的拼接 splicing test of string1 and string2
//写代码的时候要注意目标字符串的长度一定要够用 
int use_clibrary_strcat_splic_string()
{
printf("use_clibrary_strcat test: \n");
	const char* str1 = "splicing test of ";
	const char* str2 = "string";
	const char* and1 = " and ";
	int one = 1;
	int two = 2;
	//这里用到了C++中的to_string函数  c语言可以用itoa,这里主要是理解字符串拼接，，， 有int类型的字符串转换拼接常用sprintf
	int size = strlen(str1) + strlen(str2) * 2 + (strlen(to_string(one).c_str())) + strlen(and1) + (strlen(to_string(two).c_str())) + 1;
	printf("\tget the len is : %d %lu\n", size, strlen("splicing test of string1 and string2"));
	char* result = (char*)malloc(size);
	if (result == NULL)
	{
		return -1;
	}
	memset(result, '\0', size); 
	// strcpy_s(result, size, str1);
	// strcat_s(result, size, str2);
	// strcat_s(result, size, to_string(one).c_str());
	// strcat_s(result, size, and1);
	// strcat_s(result, size, str2);
	// strcat_s(result, size, to_string(two).c_str());
	//注意字符串处理时的不安全性！！！
	strcpy(result,  str1);
	strcat(result,  str2);
	strcat(result,  to_string(one).c_str());
	strcat(result,  and1);
	strcat(result,  str2);
	strcat(result,  to_string(two).c_str());
	printf("\tthe result is [%lu][%s]  \n", strlen(result), result);
	if (result != NULL)
	{
		free(result);
		result = NULL;
	}
	return 0;
}

//一般我自己用的比较多了
int use_sprintf_splic_string()
{
	printf("use_sprintf_splic_string test: \n");
	const char* str1 = "splicing test of ";
	const char* str2 = "string";
	const char* and1 = " and ";
	int one = 1;
	int two = 2;
	//定义目标字符串，为其申请内存都是必须的
	int size = strlen(str1) + strlen(str2) * 2 + (strlen(to_string(one).c_str())) + strlen(and1) + (strlen(to_string(two).c_str())) + 1;
	printf("\tget the len is : %d %lu\n", size, strlen("splicing test of string1 and string2"));
	char* result = (char*)malloc(size);
	if (result == NULL)
	{
		return -1;
	}
	memset(result, '\0', size);
	//使用sprintf一步到位
	sprintf(result, "%s%s%d%s%s%d", str1, str2, one, and1, str2, two);
	//sprintf_s(result, size, "%s%s%d%s%s%d", str1, str2, one, and1, str2, two);
	printf("\tthe result is [%lu][%s]  \n", strlen(result), result);
	if (result != NULL)
	{
		free(result);
		result = NULL;
	}
	return 0;

}

//1：类似tcp/ip协议栈，其实都是按照特定的结构体进行类型强转数据解析
void parse_struct_format_data();
//2：特定字节表示长度，对后面的数据进行处理==》其实和结构体格式差不多，柔性数组
void parse_len_and_data_networkdata();
//3：使用特定的标识符(字符或者字符串)，切割不同含义的数据，进行解析
void parse_string_spilt_data();

//这个其实都是协商好的，对端如何发送消息，我们对应的解析消息
void parse_string_test()
{
	//1：类似tcp/ip协议栈，其实都是按照特定的结构体进行类型强转数据解析
	parse_struct_format_data();
	//2：特定字节表示长度，对后面的数据进行处理==》其实和结构体格式差不多，柔性数组
	parse_len_and_data_networkdata();
	//3：使用特定的标识符(字符或者字符串)，切割不同含义的数据，进行解析
	parse_string_spilt_data();
}

//1：类似tcp/ip协议栈，其实都是按照特定的结构体进行类型强转数据解析
void parse_struct_format_data()
{
printf("parse_struct_format_data test: \n");
	//假设定义了柔性数组，构造数据并解析数据的流程如下
	struct my_data_t
	{
		int msg_type;
		int data_len;
		char data[0];
	};
	//假设要发送的数据为client send data example. \n， 类型为1
	const char* data = "client send data example. \n";
	//发送端构造最终的数据如下
	struct my_data_t *send_data = NULL;
	send_data = (struct my_data_t*)malloc(sizeof(struct my_data_t)+strlen(data)+1);
	if(send_data == NULL)
	{
		return ;
	}
	memset(send_data, '\0', sizeof(struct my_data_t)+strlen(data)+1);//只是预留了一个位而已
	send_data->msg_type = 1;
	send_data->data_len = strlen(data);
	memcpy(send_data->data, data, strlen(data));
	//这里其实 send_data就是我们最终构造的一个网络数据格式包，可以进行发送
	//使用send进行发送时，只是传参char*以及待发送的数据长度
	char *send_para_data = (char *)send_data; //结构体类型强转  tcp识别到的是这个结构里的流数据
	int send_para_data_len = sizeof(struct my_data_t)+strlen(data);
	//如果客户端使用send_para_data 及长度send_para_data_len 进行发送及校验 。。。
	//我们收到的流，以recv进行接收，就是一个字符流，内容其实就是send_para_data 长度为send_para_data_len 
	//这里要完整的接收到一个包（半包粘包问题，可参考上文）
	//进行逆向解析就好
	struct my_data_t *recv_data = (struct my_data_t*)send_para_data;
	//可以按照柔性数组的逻辑 按照长度对实际数据进行解析打印
	printf("\trecv_data type is [%d] \n",recv_data->msg_type);
	printf("\trecv_data len is [%d] \n",recv_data->data_len);
	//这里只是测试  注意如果这个data中有特殊的字符如\0等，不能这样打印，要按照十六进制按照长度打印
	printf("\trecv_data len is [%s] \n", recv_data->data);
	printf("recv_data ASSII is [");
	for(int i=0; i<recv_data->data_len; i++)
	{
		printf("%02x ", recv_data->data[i]);
	}
	printf("]\n");
	if(send_data!= NULL)
	{
		free(send_data);
		send_data = NULL;
	}
}

//2：特定字节表示长度，对后面的数据进行处理==》其实和结构体格式差不多，柔性数组
void parse_len_and_data_networkdata()
{
printf("parse_len_and_data_networkdata test: \n");
	//在网络传输中，可以用特定字节表示长度+实际数据的格式
	const char *send_data = "msg_type | msg_len |msg_data ...\n"; //实际数据有特殊字符的话，长度要传进来或者结构体其他方案
	//在网络发送中，如果想简单实现不想用结构那么麻烦，可以这样用 
	int send_len = strlen(send_data);
	printf("\tsend_len [%d][%s] \n",send_len, send_data);
	//可以类似上个函数用结构体  struct data_t{int len; char data[0];};构造
	//我试试这种：
	char * real_send_data = (char *)malloc(send_len +4+1);
	memset(real_send_data, 0, send_len +4+1);
	memcpy(real_send_data, (char*)&send_len, sizeof(int)); //前四个字节拷贝长度,也可以以字符串形式直接存进去 to_string(send_len).c_str()
	memcpy(real_send_data +sizeof(int), send_data, send_len);
	//real_send_data 就是我们实际send的流   我们可以先接收取前四个字节获取数据长度，再接收后面的字段
	//先定义一个int，从recv取四个字节，转为int表示的长度
	int recv_len = *(int *)real_send_data; //先recv取四个字节  解析成真正的数据
	char* recv_data = real_send_data+4;  //这里应该是recv读出来的 长度为recv_len 
	printf("\trecv_len[%d] [%s] \n",recv_len, recv_data);
	if(real_send_data != NULL)
	{
		free(real_send_data);
		real_send_data = NULL;
	}
}
int get_concatenate_strings(char ** result_data, int* len);
int parse_spilt_string_and_getdata(const char * data, int len);
//3：使用特定的标识符(字符或者字符串)，切割不同含义的数据，进行解析
void parse_string_spilt_data()
{
printf("parse_string_spilt_data test: \n");
	//假设协商的协议是msg_type|other_type|msg_len|msg_data 
	//构造一个数据,获取到最终发送的数据和数据长度
	char * send_data = NULL;
	int send_len = 0;
	if(get_concatenate_strings(&send_data, &send_len) < 0)
	{
		printf("\t make send_data error \n");
		return;
	}
	printf("\t last_result_data is [%d][%s] \n",send_len, send_data);
	//假设对这个数据进行发送了，报的完整性参考上文
	//接收其实就是这样格式的数据，解析一下这个 这里recv接收时要关注包的完整性
	parse_spilt_string_and_getdata(send_data, send_len);
	if(send_data != NULL)
	{
		free(send_data);
		send_data = NULL;
	}
	
}


//模拟一个完整的包，返回一个符合特定格式的拼接起来的包数据
//假设 msg_type|other_type|msg_len|msg_data 格式
int get_concatenate_strings(char ** result_data, int* len)
{
	// 假设格式msg_type|other_type|msg_len|msg_data 格式
	const char * data = "mytest of spilt of send data ... \n\t test";//注意网络数据这里的复杂，用memcpy处理
	int msg_type = 1;
	int other_type = 2;
	int msg_len = strlen(data); //实际后面的数据长度
	//这里估算一下最终最大长度 用20+strlen(data)肯定够用
	char *send_data = NULL;
	send_data = (char*)malloc(20+msg_len);
	memset(send_data, 0, 20+msg_len);
	sprintf(send_data, "%d|%d|%d|", msg_type, other_type, msg_len);
	
	//通过传参的方式传递出去
	*len = strlen(send_data) +msg_len;
	memcpy(send_data+strlen(send_data), data, msg_len);
	*result_data = send_data;
	printf("\t result_data is [%d][%s] \n",*len, send_data);
}
//定义一个保存解析后数据的结构
struct client_recv_t
{
	int msg_type;
	int dev_type;
	int data_len;
	char data[0];
};
//实际的解析函数 通过strstr进行解析
int check_recv_data_by_spilit(const char * data, int len, char **result, const char* delim);
// char * strstr(const char *haystack, const char *needle);		函数定位子串的位置，然后实现字符串的切割
// char * strcasestr(const char *haystack, const char *needle); 和strstr同样的功能，只是忽略两个参数的大小写
// char *strtok(char *str, const char *delim); 				 分解字符串 str 为一组字符串，delim 为分隔符
// char *strsep(char **stringp, const char *delim);          strtok的升级,

int parse_spilt_string_and_getdata(const char * data, int len)
{
	printf("\tneed parse data is [%d][%s] \n", len, data);
	//使用特定字符串对字符串进行切割，这里举例是"|"，可以是其他的字符串。。。
	//协商的协议是：msg_type|other_type|msg_len|msg_data 
	//实际数据是：[47][1|2|40|mytest of spilt of send data ... \n\t test

	char * result_data_t = NULL; //存储解析后的数据，也可以是其他方案，这里只是举例
	//这里使用"|"对长度为len目标数据data进行切割，通过传参获取 result_data_t
	if(check_recv_data_by_spilit(data, len, &result_data_t, "|") != 0)
	{
		printf("vps parse spilit error \n");
		return -1;
	}
	//对解析后的数据进行打印
	struct client_recv_t *result_data = (struct client_recv_t *)result_data_t;
	printf("\t parse test data is [%d][%d][%d][%s] \n", result_data->msg_type, result_data->dev_type, result_data->data_len, result_data->data);
	// memset(result_data, sizeof(struct client_recv_t)+ (result_data->data_len+1), 0);
	if(result_data != NULL)
	{
		free(result_data);
		result_data = NULL;
		printf("free success \n");
	}
	return 0;
}

//这里使用"|"对长度为len目标数据data进行切割，通过参数result返回解析后的数据
//
int check_recv_data_by_spilit(const char * data, int len, char **result, const char* delim)
{
	//直接根据'|'对字符串进行切割 根据切割后的个数进行校验
	char *src = strdup(data); //复制了一份数据 strsep会修改原字符串
	char * src_free = src; 

	//msg_type|other_type|msg_len|msg_data  按照协商的格式 定义了暂存的指针
	char * delim_buff[4] = {0};
	char* token = NULL;  //切割后返回的字符指针
	int i = 0;			 //切割的个数
	//返回的是符合的分割的字符   同时原字符从分割后的位置开始
	for(token = strsep(&src, delim); token!=NULL && i<4; token=strsep(&src, delim))
	{
		delim_buff[i++] = token;
		printf("\tspilt data [%d:%lu:%s] \n", i, strlen(token), token);
		printf("\t\t src:[%s] \n",src);
	}

	if(i != 4) //必然是协商的格式 
	{
		printf("\tvps spilit data error \n");
		free(src_free);
		return -1;
	}

	int msg_type = (int)atoi(delim_buff[0]);
	int dev_type = (int)atoi(delim_buff[1]);
	int data_len = (int)atoi(delim_buff[2]);
	char * cli_data = delim_buff[3];
	printf("\nmsg_type:%d, dev_type:%d, data_len:%d:%lu:[%s] \n", msg_type, dev_type, data_len, strlen(cli_data), cli_data);

	//把解析后的数据进行处理 使用传参传出去
	int ret = 0;
	struct client_recv_t *result_t = NULL;
	result_t = (struct client_recv_t *)malloc(sizeof(struct client_recv_t) + data_len+1);
	if(result_t == NULL)
	{
		printf("malloc error \n");
		*result = NULL;
		ret = -1;
	}else
	{
		memset(result_t, sizeof(struct client_recv_t) + data_len+1, 0);
		result_t->msg_type = msg_type;
		result_t->dev_type = dev_type;
		result_t->data_len = data_len;
		memcpy(result_t->data, cli_data, data_len);
		
		*result = (char *)result_t;
	}

	memset(src_free, 0, len);
	free(src_free);
	src_free = NULL;

	return ret;

}